#!/bin/python
# Copyright 2008 Clint Banis.  All rights reserved.
#
# This script serves as the master module getting the software path right
# before importing all sub modules into its namespace.
#
# The main function is to run the Application ware which implements a
# command-line configurable implementation of the desktop rendering.
#
# USAGE:
#     mayan_cal/script.py --day=1983,7,26 --install-wallpaper --watermark=auto
#
# This script is compiled as the frozen executable entry point giving access
# to the rest of the functionality.
#
__version__ = '1.0'

from sys import path as system_path
from os.path import exists, splitext, join as joinpath
from os.path import sep as PATH_SEP, dirname, basename
from os import chdir, environ
from time import sleep
from traceback import print_exc as traceback

from optparse import OptionParser
from pdb import set_trace as debug, runcall

IMPORTS = '''
from mayan_cal import gregarian_date
from mayan_cal.affirmation import daily_affirmation
from mayan_cal.drawing import Backend, Wallpaper, saveImageTo, compositeWatermark
from mayan_cal.desktop import Platform
'''

DESKTOP_RESOLUTION = (1280, 1200)

STANDARD_ENDING = '\Local Settings\Application Data\Microsoft\Wallpaper1.bmp'

def define_application():
    # Use via setup_shell after importing necessary mayan_cal modules.
    ## When application programming doesn't need to be deferred until after
    ## the shell (system package paths and cwd) is setup, this becomes a
    ## global construct.
    global Application
    class Application(Backend):
        'Provides a command-line front end for utilizing the Wallpaper interface.'

        class CommandLine(OptionParser):
            USAGE   = '%prog'
            VERSION = ('%%prog %s' % __version__)

            def __init__(self):
                usage = Application.CommandLine.USAGE
                version = Application.CommandLine.VERSION

                OptionParser.__init__(self, usage = usage, version = version)

                self.set_defaults(day = todays_date(),
                                  render_bitmap = True,
                                  affirmation_file = None,
                                  watermark_background = 'preferred',
                                  install_wallpaper = True)

                # Control operations.
                self.add_option("-p", "--preferred-background",
                                  help = "Install a preferred background into the Windows Registry.\n")

                # Input options.
                self.add_option("-d", "--day", dest = "day",
                                  help = "Specify source day.\n")
                self.add_option("-f", "--affirmation-file", "--file",
                                  help = "Specify source affirmation wallpaper rendering.\n")

                self.add_option("-m", "--watermark-background", "--watermark",
                                  help = "Program watermark filename or date specifier.\n")

                self.add_option("-t", "--watermark-mode",
                                  help = "program watermark positioning and translucence")

                self.add_option("-s", "--screen-resolution",
                                  help = "specify resulting background width-height constraints")

                # Output options.
                self.add_option("-r", "--render-bitmap", "--render-bmp", "--bmp",
                                  dest = "render_bitmap",
                                  action = "store_true",
                                  help = "perform Windows bitmap conversion")

                self.add_option("--no-render-bitmap",
                                  dest = "render_bitmap",
                                  action = "store_false",
                                  help = "do not perform bitmap conversion")

                self.add_option("-i", "--install-wallpaper",
                                  dest = "install_wallpaper",
                                  action = "store_true",
                                  help = "install wallpaper onto the Windows desktop")

                self.add_option("--no-install-wallpaper", 
                                  dest = "install_wallpaper",
                                  action = "store_false",
                                  help = "do not install wallpaper onto Windows desktop")

                self.add_option("-o", "--output-dir",
                                  help = "Location to put temporary drawing files.")
                self.add_option("--tribe-dir",
                                  help = "Location of tribe glyphs.")
                self.add_option("--tone-dir",
                                  help = "Location of tone glyphs.")
                self.add_option("-g", "--glyph-dir",
                                  help = "Location of tribe and tone glyph directories.")

                self.add_option("-v", "--verbose",
                                  dest = "verbose", action = "store_true",
                                  help = "be verbose when operating")

                self.add_option("-q", "--quiet",
                                  dest = "verbose", action = "store_false",
                                  help = "be non-verbose when operating")

                # Internal Debug
                self.add_option("-e", "--debug",
                                  action = "store_true",
                                  help = "Enter internal debugging of the run frame.")

        def __init__(self):
            # Initial folder set to platform configuration:
            Backend.__init__(self, output_dir = self['WallpaperTempDir'],
                                   glyph_dir  = self['MayanGlyphs'     ],
                                   tribe_dir  = self['MayanTribeDir'   ],
                                   tone_dir   = self['MayanToneDir'    ])

            # Now this doesn't make the most sense:  This class wants to reuse
            # the parser, so that you can pass multiple shell invocations throgh
            # one Application via a gui front-end, but the above application state
            # isn't formalized in a way that the application can be reset between
            # runs.  No big deal.

        @property
        def cmdln_parser(self):
            # Make one instance!
            try: return self._parser
            except AttributeError:
                # See how this encapsulates the parser routine!
                p = self._parser = self.CommandLine().parse_args
                return p

            ##    return self.CommandLine().parse_args

        def get_shell_options(self, argv = None):
            if argv is None:
                from sys import argv

            return self.cmdln_parser(argv)

        def run(self, argv = None, options = None, args = None):
            # This function attempt to resolve a 'target' after post-image-processing,
            # which becomes the path required for a Windows Registry Desktop install.
            if options is None:
                assert args is None
                (options, args) = self.get_shell_options(argv = argv)

            if options.debug:
                # Start tracing this frame.
                debug()

            # First see if the preferred background is specified, and if so,
            # do nothing but install the argument in the reg/config and exit.
            bg = self.get_preferred_background(options.preferred_background,
                                               options.verbose)

            # Alternately do folder set to any directories specified on command line.
            self.folders = dict(output_dir = options.output_dir or self['WallpaperTempDir'],
                                glyph_dir  = options.glyph_dir  or self['MayanGlyphs'],
                                tribe_dir  = options.tribe_dir  or self['MayanTribes'],
                                tone_dir   = options.tone_dir   or self['MayanTones'])

            # Determine either wallpaper or day, not both.
            # assert (bool(options.affirmation_file) ^ bool(type(options.day) is not date))

            target = self.get_affirmation_file(options.day, options.verbose) \
                       or options.affirmation_file

            if options.verbose:
                self.log("\t%r" % target)

            if options.screen_resolution or True:
                # Resolve options.SCREEN_RESOLUTION from arguments.
                # This is so following functions can take advantage of
                # the setting.  The resize action can occur if no others
                # utilized the resolution.
                options.SCREEN_RESOLUTION = DESKTOP_RESOLUTION

            target = self.get_watermark_background(options.watermark_background,
                                                   target, # As source.
                                                   options.SCREEN_RESOLUTION,
                                                   options.verbose) or target

            if options.render_bitmap:
                # Convert to Windows Bitmap and store somewhere.
                if options.verbose:
                    self.log("Converting to Windows Bitmap...")

                converted_path = self['CurrentWallpaperPathname'] or 'CurrentWallpaper.bmp'
                converted_path = joinpath(self.FOLDERS.OUTPUT, converted_path)
                 # rename_extension(target, 'bmp')
                target = saveImageTo(target, converted_path)

                if options.verbose:
                    self.log("\t%r" % target)

            # Install to Windows desktop option.
            if options.install_wallpaper:
                if options.verbose:
                    self.log("Installing Windows background desktop wallpaper...")
                    self.log("\t%r" % target)

                Platform.installWallpaper(target)

        def get_preferred_background(self, bg, verbose):
            if bg:
                if bg == 'auto':
                    # Determine from currently installed system wallpaper.
                    ##    converted = self.getConfigUserSystem('ConvertedWallpaper')
                    bg = self.getConfigUserSystem('Wallpaper')
                    ##    if current.endswith(STANDARD_ENDING) and converted != current:
                    ##        bg = converted
                    ##    else:
                    ##        raise RuntimeError("No automatic background determinable:\n\t%r\n\t%r" % (converted, current))

                    assert bg, RuntimeError("No automatic background determinable!")

                elif bg == 'none':
                    bg = None

                if bg is None:
                    # Remove preferred background option from registry.
                    # If the registry value is "none", this doesn't happen.
                    if verbose:
                        self.log("Removing Preferred Background setting from registry...")

                    # Todo: Fail safe.
                    self.delConfigUserSystem('PreferredBackground')

                else:
                    if verbose:
                        self.log("Setting Preferred Background to %r..." % bg)

                    # XXX Requires absolute path normalization upon background path.
                    self.setConfigUserSystem('PreferredBackground', bg)

            ##    from sys import exit as sys_exit
            ##    sys_exit(0) # Success

            return bg

        def get_affirmation_file(self, day, verbose):
            # Formulate the first target based on the affirmations wallpaper
            # created for the day, which might be parsed from the command line.
            if day:
                # Configure affirmation via Wallpaper.
                if type(day) is gregarian_date:
                    af = daily_affirmation(day)

                elif True:
                    # Attempt to construct a date from the string.
                    print day
                    af = daily_affirmation(self.parse_datestring(day))

                # Use day programming to construct Wallpaper rendering.
                wallpaper = Wallpaper(af, backend = self)

                # This executes the save function to ensure a rendered file.
                if verbose:
                    self.log("Generating Affirmation Wallpaper file...")

                return (wallpaper.full_saved_path)

        def get_watermark_background(self, watermark, source, resolution, verbose):
            if watermark:
                if type(watermark) is str and watermark.lower() in ("auto", "preferred"):
                    watermark = self.getConfigUserSystem('PreferredBackground')
                    if not watermark:
                        watermark = None
                        if verbose:
                            self.log("No Preferred Background found!")
                    else:
                        if verbose:
                            self.log("Using Preferred Background Watermark %r." % watermark)

                if type(watermark) is str and watermark.lower() == "none":
                    watermark = None

                if watermark:
                    # Compose a watermarked image.
                    if verbose:
                        self.log("Composing Background with Wallpaper Watermark...")
                    filename, ext = splitext(basename(source))

                    bg = splitext(basename(watermark))[0]
                    composition = '%s on %s%s' % (filename, bg, ext)

                    # Draw the temporary watermarked background image file.
                    bg = joinpath(dirname(source), composition)
                    compositeWatermark(watermark,
                                       source,
                                       resolution,
                                       bg)

                    if verbose:
                        self.log("\t%r" % bg)

                    return bg

        month_names = {'january': 1, 'february': 2, 'march': 3, 'april': 4,
                       'may': 5, 'june': 6, 'july': 7, 'august': 8,
                       'september': 9, 'october': 9, 'november': 11, 'december': 12}

        def parse_datestring(self, datestr):
            month, day, year = datestr.split()
            year = int(year)
            day = int(day)

            month = self.month_names.get(month.lower()) or int(month)

            return gregarian_date(year, month, day)

        ConfigKey = getattr(Platform, 'DesktopKey', None)
        # ConfigKey += 'MayanCalendar\\'

        def getConfigUserSystem(self, key, default = None):
            if Platform and self.ConfigKey:
                try: return Platform.RegistryAPI[self.ConfigKey + key]
                except WindowsError:
                    pass

            return default

        def setConfigUserSystem(self, key, value):
            if Platform and self.ConfigKey:
                try: Platform.RegistryAPI[self.ConfigKey + key] = value
                except WindowsError:
                    raise # What else?

        __getitem__ = getConfigUserSystem
        __setitem__ = setConfigUserSystem

        def delConfigUserSystem(self, key):
            pass

        def log(self, message):
            try: stream = getattr(self, 'logStream')
            except AttributeError:
                from sys import stdout as stream

            print >> stream, message

        def runForLog(self, function, *args, **kwd):
            try: prev = self.logStream
            except AttributeError:
                prev = AttributeError

            from cStringIO import StringIO
            buf = self.logStream = StringIO()

            # Ignores value of function.
            try: function(*args, **kwd)
            finally:
                if prev is AttributeError:
                    del self.logStream
                else:
                    self.logStream = prev

                return buf.getvalue()

    return Application

def todays_date():
    # Time might be relative, but not on the desktop.
    from time import localtime as time, mktime
    return gregarian_date.fromtimestamp(mktime(time()))

def rename_extension(filename, new_ext):
    # Express a filename but with a different extension.
    return '%s.%s' % (splitext(filename)[0], new_ext)

def make_application(*args, **kwd):
    app = define_application()
    return app(*args, **kwd)

def setup_shell(script = None):
    if script is None:
        script = globals().get('__file__', None)

    if script:
        package_path = dirname(dirname(script)).strip()
        if package_path == '':
            package_path = '.'

        # Makes this product module accessible to the script environment.
        system_path.append(package_path)

        if package_path != '.':
            chdir(package_path)

    exec IMPORTS in globals(), globals()

def main():
    setup_shell()

    try:
        # from pdb import runcall as apply
        apply(make_application().run)

    except SystemExit:
        pass
    except:
        # This is to facilitate watching the console error during headless (scheduled) invocation.
        # Don't do this for production environments (via Scheduled Task).
        traceback()

        try: line = raw_input(' > ')
        except KeyboardInterrupt:
            pass
        else:
            if line == 'debug':
                debug()

if __name__ == '__main__':
    main()
